Pinvon's Blog

所见, 所闻, 所思, 所想

ReactNative 例子

新建项目

npm info react-native  # 查看版本信息
react-native init App --version 0.51.0  # 版本号可以通过上一步得到
cd App
react-native run-android

Hello World

进入项目根目录, 将 App.js 下的代码替换如下:

import React, { Component } from 'react';
import { Text } from 'react-native';

export default class HelloWorldApp extends Component {
  render() {
    return (
      <Text>Hello world!</Text>
    );
  }
}

执行 react-native run-android, 打开程序, 可以看到 Hello World.

展示电影程序

场景: 从电影数据库中取得最近正在上映的 25 部电影, 并在一个 FlatList 中展示出来.

展示模拟数据

将以下代码覆盖到 App.js 文件:

import React, { Component } from 'react';
import { 
  AppRegistry,
  Image,
  StyleSheet,
  Text,
  View,
} from 'react-native';

var MOCKED_MOVIES_DATA = [
  {title: '标题', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
];

export default class HelloWorldApp extends Component {
  render() {
    var movie = MOCKED_MOVIES_DATA[0];
    return (
      <View style={styles.container}>
        <Text>{movie.title}</Text>
        <Text>{movie.year}</Text>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}
        />
      </View>
    );
  }
}

// 添加样式
var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
});

修改样式

如果要将文字显示在图片的右边, 再放大标题, 需要修改样式.

我们增加一个 container, 实现在一个水平布局内嵌套一个垂直布局.

修改部分代码:

export default class HelloWorldApp extends Component {
  render() {
    var movie = MOCKED_MOVIES_DATA[0];
    return (
      <View style={styles.container}>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}
        />
        <View style={styles.rightContainer}>
          <Text>{movie.title}</Text>
          <Text>{movie.year}</Text>
        </View>
      </View>
    );
  }
}

// 添加样式
var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 20,
    marginBottom: 8,
    textAlign: 'center',
  },
  year: {
    textAlign: 'center',
  },
});

拉取真正的数据

将 REQUEST_URL 常量放到 import 下面:

var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/0.51-stable/docs/MoviesExample.json';

初始化(放在 render() 之前):

constructor(props) {
  super(props);
  this.state = {
    movies: null,
  };
  this.fetchData = this.fetchData.bind(this);
}

组件加载完毕后, 就可以向服务器请求数据了. componentDidMount() 是 React 组件的一个生命周期方法, 会在组件刚加载完成的时候调用一次, 以后不再调用.

componentDidMount() {
  this.fetchData();
}

fetchData():

fetchData() {
  fetch(REQUEST_URL)
  .then((response) => response.json())
  .then((responseData) => {
    this.setState({
      movies: responseData.movies,
    });
  });
}

修改 render():

render() {
  if (!this.state.movies) {
    return this.renderLoadingView();
  }

  var movie = this.state.movies[0];
  return this.renderMovie(movie);
}

renderLoadingView() {
  return (
    <View style={styles.container}>
      <Text>正在加载电影数据...</Text>
    </View>
  );
}

renderMovie(movie) {
  return (
    <View style={styles.container}>
      <Image
        source={{uri: movie.posters.thumbnail}}
        style={styles.thumbnail}
      />
      <View style={styles.rightContainer}>
        <Text style={styles.title}>{movie.title}</Text>
        <Text style={styles.year}>{movie.year}</Text>
      </View>
    </View>
  );
}

FlatList

FlatList 只显示当前屏幕上的元素, 对于那些已经渲染好了, 但移动到了屏幕之外的元素, 则会从原生视图结构中移除, 以提高性能.

先在文件最开头引入 FlatList:

import {
    ...
    FlatList,
    ...
} from 'react-native';

修改 render():

render() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <FlatList
        data={this.state.data}
        renderItem={this.renderMovie}
        style={styles.list}
      />
    );
  }

修改 constructor():

constructor(props) {
    super(props);
    this.state = {
      data: [],
      loaded: false,
    };
    // 在ES6中,如果在自定义的函数里使用了this关键字,则需要对其进行“绑定”操作,否则this的指向不对
    // 像下面这行代码一样,在constructor中使用bind是其中一种做法(还有一些其他做法,如使用箭头函数等)
    this.fetchData = this.fetchData.bind(this);
  }

修改 fetchData():

fetchData() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        // 注意,这里使用了this关键字,为了保证this在调用时仍然指向当前组件,我们需要对其进行“绑定”操作
        this.setState({
          data: this.state.data.concat(responseData.movies),
          loaded: true,
        });
      });
  }

添加样式:

list: {
    paddingTop: 20,
    backgroundColor: '#F5FCFF',
  },

调试

使用 Chrome 来调试 React Native 程序

启动远程调试

摇晃手机, 点击 Debug JS Remotely.

同时会电脑会自动打开一个页面: http://localhost:8081/debugger-ui

打开 Chrome 开发者工具

按下 F12.

Sources 面板下, 可以看到 debuggerWorker.js. 将其层层打开, 可以看到我们的程序.

常见错误

Unable to load script from assets 'index.android.bundle'.

进入项目根目录:

mkdir -p android/app/src/main

react-native bundle --platform android --dev false --entry-file index.js   --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

react-native run-android

我们可以把第二条命令写到 package.json 的 "script" 字段(与 "dependencies" 字段同级)中:

"android-linux": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res && react-native run-android"

然后通过 npm run android-linux 即可.

Could not connect to development server

adb reverse tcp:8081 tcp:8081

# 如果还不行

npm start

如果提示端口占用:

sudo lsof -i :8081
kill -9 xxx

Comments

使用 Disqus 评论
comments powered by Disqus